﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework.Resources;

namespace Microsoft.Xna.Framework
{
    /// <summary>
    /// 	Defines a circle.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct BoundingCircle : IEquatable<BoundingCircle>
    {
        #region Static Operations

        #region Create From

        /// <summary>
        /// 	Creates a BoundingCircle that can contain a specified list of points.
        /// </summary>
        /// <param name="points"> List of points the BoundingCircle must contain. </param>
        public static BoundingCircle CreateFromPoints(IEnumerable<Vector2> points)
        {
            if (points == null)
            {
                throw new ArgumentNullException("points");
            }
            var enumerator = points.GetEnumerator();
            if (!enumerator.MoveNext())
            {
                throw new ArgumentException(Resx.BoundingCircleZeroPoints);
            }
            var mostRightPoint = enumerator.Current;
            var mostLeftPoint = enumerator.Current;
            var mostUpPoint = enumerator.Current;
            var mostDownPoint = enumerator.Current;
            foreach (var point in points)
            {
                if (mostRightPoint.X > point.X)
                {
                    mostRightPoint = point;
                }
                if (mostLeftPoint.X < point.X)
                {
                    mostLeftPoint = point;
                }
                if (mostUpPoint.Y > point.Y)
                {
                    mostUpPoint = point;
                }
                if (mostDownPoint.Y < point.Y)
                {
                    mostDownPoint = point;
                }
            }
            float horizontalDistance;
            Vector2.Distance(ref mostLeftPoint, ref mostRightPoint, out horizontalDistance);
            float verticalDistance;
            Vector2.Distance(ref mostDownPoint, ref mostUpPoint, out verticalDistance);
            Vector2 center;
            float radius;
            if (horizontalDistance > verticalDistance)
            {
                Vector2.Lerp(ref mostLeftPoint, ref mostRightPoint, 0.5f, out center);
                radius = horizontalDistance * 0.5f;
            }
            else
            {
                Vector2.Lerp(ref mostDownPoint, ref mostUpPoint, 0.5f, out center);
                radius = verticalDistance * 0.5f;
            }
            foreach (var point in points)
            {
                Vector2 distanceToCenterVector;
                distanceToCenterVector.X = point.X - center.X;
                distanceToCenterVector.Y = point.Y - center.Y;
                var distanceToCenter = distanceToCenterVector.Length();
                if (distanceToCenter > radius)
                {
                    radius = (radius + distanceToCenter) * 0.5f;
                    center += (1f - (radius / distanceToCenter)) * distanceToCenterVector;
                }
            }
            return new BoundingCircle(center, radius);
        }

        /// <summary>
        /// 	Creates the smallest BoundingCircle that can contain a specified BoundingRectangle.
        /// </summary>
        /// <param name="rectangle"> The BoundingRectangle to create the BoundingCircle from. </param>
        public static BoundingCircle CreateFromBoundingRectangle(BoundingRectangle rectangle)
        {
            BoundingCircle circle;
            Vector2.Lerp(ref rectangle.Min, ref rectangle.Max, 0.5f, out circle.Center);
            float diameter;
            Vector2.Distance(ref rectangle.Min, ref rectangle.Max, out diameter);
            circle.Radius = diameter * 0.5f;
            return circle;
        }

        /// <summary>
        /// 	Creates the smallest BoundingCircle that can contain a specified BoundingRectangle.
        /// </summary>
        /// <param name="rectangle"> The BoundingRectangle to create the BoundingCircle from. </param>
        /// <param name="result"> [OutAttribute] The created BoundingCircle. </param>
        public static void CreateFromBoundingRectangle(ref BoundingRectangle rectangle, out BoundingCircle result)
        {
            Vector2.Lerp(ref rectangle.Min, ref rectangle.Max, 0.5f, out result.Center);
            float diameter;
            Vector2.Distance(ref rectangle.Min, ref rectangle.Max, out diameter);
            result.Radius = diameter * 0.5f;
        }

        #endregion

        #region Create Merged

        /// <summary>
        /// 	Creates a BoundingCircle that contains the two specified BoundingCircle instances.
        /// </summary>
        /// <param name="original"> BoundingCircle to be merged. </param>
        /// <param name="additional"> BoundingCircle to be merged. </param>
        public static BoundingCircle CreateMerged(BoundingCircle original, BoundingCircle additional)
        {
            Vector2 distanceVector;
            Vector2.Subtract(ref additional.Center, ref original.Center, out distanceVector);
            var distance = distanceVector.Length();
            var radius = original.Radius;
            var otherRadius = additional.Radius;
            if ((radius + otherRadius) >= distance)
            {
                if ((radius - otherRadius) >= distance)
                {
                    return original;
                }
                if ((otherRadius - radius) >= distance)
                {
                    return additional;
                }
            }
            var distanceUnit = (distanceVector * (1f / distance));
            var minRadius = MathHelper.Min(-radius, distance - otherRadius);
            var maxRadius = (MathHelper.Max(radius, distance + otherRadius) - minRadius) * 0.5f;
            return new BoundingCircle(original.Center + ((distanceUnit * (maxRadius + minRadius))), maxRadius);
        }

        /// <summary>
        /// 	Creates a BoundingCircle that contains the two specified BoundingCircle instances.
        /// </summary>
        /// <param name="original"> BoundingCircle to be merged. </param>
        /// <param name="additional"> BoundingCircle to be merged. </param>
        /// <param name="result"> [OutAttribute] The created BoundingCircle. </param>
        public static void CreateMerged(ref BoundingCircle original, ref BoundingCircle additional, out BoundingCircle result)
        {
            Vector2 distanceVector;
            Vector2.Subtract(ref additional.Center, ref original.Center, out distanceVector);
            var distance = distanceVector.Length();
            var radius = original.Radius;
            var otherRadius = additional.Radius;
            if ((radius + otherRadius) >= distance)
            {
                if ((radius - otherRadius) >= distance)
                {
                    result = original;
                    return;
                }
                if ((otherRadius - radius) >= distance)
                {
                    result = additional;
                    return;
                }
            }
            var distanceUnit = (distanceVector * (1f / distance));
            var minRadius = MathHelper.Min(-radius, distance - otherRadius);
            var maxRadius = (MathHelper.Max(radius, distance + otherRadius) - minRadius) * 0.5f;
            result.Center = original.Center + ((distanceUnit * (maxRadius + minRadius)));
            result.Radius = maxRadius;
        }

        #endregion

        #region Equality & Inequality

        /// <summary>
        /// 	Returns a value that indicates whether two specified BoundingCircle values are equal.
        /// </summary>
        /// <returns> true if <paramref name="left" /> and <paramref name="right" /> are equal; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator ==(BoundingCircle left, BoundingCircle right)
        {
            return left.Equals(right);
        }

        /// <summary>
        /// 	Returns a value that indicates whether two specified BoundingCircle values are not equal.
        /// </summary>
        /// <returns> true if <paramref name="left" /> and <paramref name="right" /> are not equal; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator !=(BoundingCircle left, BoundingCircle right)
        {
            if (left.Center == right.Center)
            {
                return left.Radius != right.Radius;
            }
            return true;
        }

        #endregion

        #endregion

        #region Fields & Properties

        /// <summary>
        /// 	The center point of the circle.
        /// </summary>
        public Vector2 Center;

        /// <summary>
        /// 	The radius of the circle.
        /// </summary>
        public float Radius;

        #endregion

        #region Constructors

        /// <summary>
        /// 	Creates a new instance of BoundingCircle.
        /// </summary>
        /// <param name="center"> Center point of the circle. </param>
        /// <param name="radius"> Radius of the circle. </param>
        public BoundingCircle(Vector2 center, float radius)
        {
            if (radius < 0f)
            {
                throw new ArgumentException(Resx.NegativeRadius);
            }
            Center = center;
            Radius = radius;
        }

        #endregion

        #region Implementation of IEquatable<BoundingCircle>

        /// <summary>
        /// 	Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns> true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false. </returns>
        /// <param name="other"> An object to compare with this object. </param>
        public bool Equals(BoundingCircle other)
        {
            return Center == other.Center && Radius == other.Radius;
        }

        #endregion

        #region Overrides of ValueType

        /// <summary>
        /// 	Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <returns> true if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, false. </returns>
        /// <param name="obj"> Another object to compare to. </param>
        public override bool Equals(object obj)
        {
            var result = false;
            if (obj != null && obj is BoundingCircle)
            {
                result = Equals((BoundingCircle) obj);
            }
            return result;
        }

        /// <summary>
        /// 	Returns the hash code for this instance.
        /// </summary>
        /// <returns> A 32-bit signed integer that is the hash code for this instance. </returns>
        public override int GetHashCode()
        {
            return Center.GetHashCode() + Radius.GetHashCode();
        }

        /// <summary>
        /// 	Returns the fully qualified type name of this instance.
        /// </summary>
        /// <returns> A <see cref="T:System.String" /> containing a fully qualified type name. </returns>
        public override string ToString()
        {
            var currentCulture = CultureInfo.CurrentCulture;
            return string.Format(currentCulture, "{{Center:{0} Radius:{1}}}", Center, Radius.ToString(currentCulture));
        }

        #endregion

        #region Contains

        /// <summary>
        /// 	Checks whether the current BoundingCircle contains the specified point.
        /// </summary>
        /// <param name="point"> The point to check against the current BoundingCircle. </param>
        public ContainmentType Contains(Vector2 point)
        {
            if (Vector2.DistanceSquared(point, Center) >= (Radius * Radius))
            {
                return ContainmentType.Disjoint;
            }
            return ContainmentType.Contains;
        }

        /// <summary>
        /// 	Checks whether the current BoundingCircle contains the specified point.
        /// </summary>
        /// <param name="point"> The point to test for overlap. </param>
        /// <param name="result"> [OutAttribute] Enumeration indicating the extent of overlap. </param>
        public void Contains(ref Vector2 point, out ContainmentType result)
        {
            float distanceSquared;
            Vector2.DistanceSquared(ref point, ref Center, out distanceSquared);
            result = (distanceSquared < (Radius * Radius)) ? ContainmentType.Contains : ContainmentType.Disjoint;
        }

        /// <summary>
        /// 	Checks whether the current BoundingCircle contains the specified BoundingCircle.
        /// </summary>
        /// <param name="circle"> The BoundingCircle to check against the current BoundingCircle. </param>
        public ContainmentType Contains(BoundingCircle circle)
        {
            float distance;
            Vector2.Distance(ref Center, ref circle.Center, out distance);
            var radius = Radius;
            var otherRadius = circle.Radius;
            if ((radius + otherRadius) >= distance)
            {
                if ((radius - otherRadius) >= distance)
                {
                    return ContainmentType.Contains;
                }
                return ContainmentType.Intersects;
            }
            return ContainmentType.Disjoint;
        }

        /// <summary>
        /// 	Checks whether the current BoundingCircle contains the specified BoundingCircle.
        /// </summary>
        /// <param name="circle"> The BoundingCircle to test for overlap. </param>
        /// <param name="result"> [OutAttribute] Enumeration indicating the extent of overlap. </param>
        public void Contains(ref BoundingCircle circle, out ContainmentType result)
        {
            float distance;
            Vector2.Distance(ref Center, ref circle.Center, out distance);
            var radius = Radius;
            var otherRadius = circle.Radius;
            result = ((radius + otherRadius) >= distance) ? (((radius - otherRadius) >= distance) ? ContainmentType.Contains : ContainmentType.Intersects) : ContainmentType.Disjoint;
        }

        /// <summary>
        /// 	Checks whether the current BoundingCircle contains the specified BoundingRectangle.
        /// </summary>
        /// <param name="rectangle"> The BoundingRectangle to check against the current BoundingCircle. </param>
        public ContainmentType Contains(BoundingRectangle rectangle)
        {
            if (!rectangle.Intersects(this))
            {
                return ContainmentType.Disjoint;
            }
            Vector2 distanceVector;
            var radiusSquared = Radius * Radius;
            distanceVector.X = Center.X - rectangle.Min.X;
            distanceVector.Y = Center.Y - rectangle.Max.Y;
            if (distanceVector.LengthSquared() > radiusSquared)
            {
                return ContainmentType.Intersects;
            }
            distanceVector.X = Center.X - rectangle.Max.X;
            distanceVector.Y = Center.Y - rectangle.Max.Y;
            if (distanceVector.LengthSquared() > radiusSquared)
            {
                return ContainmentType.Intersects;
            }
            distanceVector.X = Center.X - rectangle.Max.X;
            distanceVector.Y = Center.Y - rectangle.Min.Y;
            if (distanceVector.LengthSquared() > radiusSquared)
            {
                return ContainmentType.Intersects;
            }
            distanceVector.X = Center.X - rectangle.Min.X;
            distanceVector.Y = Center.Y - rectangle.Min.Y;
            if (distanceVector.LengthSquared() > radiusSquared)
            {
                return ContainmentType.Intersects;
            }
            return ContainmentType.Contains;
        }

        /// <summary>
        /// 	Checks whether the current BoundingCircle contains the specified BoundingRectangle.
        /// </summary>
        /// <param name="rectangle"> The BoundingRectangle to test for overlap. </param>
        /// <param name="result"> [OutAttribute] Enumeration indicating the extent of overlap. </param>
        public void Contains(ref BoundingRectangle rectangle, out ContainmentType result)
        {
            bool intersacts;
            rectangle.Intersects(ref this, out intersacts);
            if (!intersacts)
            {
                result = ContainmentType.Disjoint;
            }
            else
            {
                Vector2 distanceVector;
                var radiusSquared = Radius * Radius;
                result = ContainmentType.Intersects;
                distanceVector.X = Center.X - rectangle.Min.X;
                distanceVector.Y = Center.Y - rectangle.Max.Y;
                if (distanceVector.LengthSquared() > radiusSquared)
                {
                    return;
                }
                distanceVector.X = Center.X - rectangle.Max.X;
                distanceVector.Y = Center.Y - rectangle.Max.Y;
                if (distanceVector.LengthSquared() > radiusSquared)
                {
                    return;
                }
                distanceVector.X = Center.X - rectangle.Max.X;
                distanceVector.Y = Center.Y - rectangle.Min.Y;
                if (distanceVector.LengthSquared() > radiusSquared)
                {
                    return;
                }
                distanceVector.X = Center.X - rectangle.Min.X;
                distanceVector.Y = Center.Y - rectangle.Min.Y;
                if (distanceVector.LengthSquared() > radiusSquared)
                {
                    return;
                }
                result = ContainmentType.Contains;
            }
        }

        #endregion

        #region Intersects

        /// <summary>
        /// 	Checks whether the current BoundingCircle intersects with a specified BoundingCircle.
        /// </summary>
        /// <param name="circle"> The BoundingCircle to check for intersection with the current BoundingCircle. </param>
        public bool Intersects(BoundingCircle circle)
        {
            float distanceSquared;
            Vector2.DistanceSquared(ref Center, ref circle.Center, out distanceSquared);
            var radiusDistance = Radius + circle.Radius;
            return (radiusDistance * radiusDistance) > distanceSquared;
        }

        /// <summary>
        /// 	Checks whether the current BoundingCircle intersects another BoundingCircle.
        /// </summary>
        /// <param name="circle"> The BoundingCircle to check for intersection with. </param>
        /// <param name="result"> [OutAttribute] true if the BoundingCircle instances intersect; false otherwise. </param>
        public void Intersects(ref BoundingCircle circle, out bool result)
        {
            float distanceSquared;
            Vector2.DistanceSquared(ref Center, ref circle.Center, out distanceSquared);
            var radiusDistance = Radius + circle.Radius;
            result = (radiusDistance * radiusDistance) > distanceSquared;
        }

        /// <summary>
        /// 	Checks whether the current BoundingCircle intersects with a specified BoundingRectangle.
        /// </summary>
        /// <param name="rectangle"> The BoundingRectangle to check for intersection with the current BoundingCircle. </param>
        public bool Intersects(BoundingRectangle rectangle)
        {
            Vector2 closestPoint;
            Vector2.Clamp(ref Center, ref rectangle.Min, ref rectangle.Max, out closestPoint);
            float closestDistanceSquared;
            Vector2.DistanceSquared(ref Center, ref closestPoint, out closestDistanceSquared);
            return (closestDistanceSquared <= (Radius * Radius));
        }

        /// <summary>
        /// 	Checks whether the current BoundingCircle intersects a BoundingRectangle.
        /// </summary>
        /// <param name="rectangle"> The BoundingRectangle to check for intersection with. </param>
        /// <param name="result"> [OutAttribute] true if the BoundingCircle and BoundingRectangle intersect; false otherwise. </param>
        public void Intersects(ref BoundingRectangle rectangle, out bool result)
        {
            Vector2 closestPoint;
            Vector2.Clamp(ref Center, ref rectangle.Min, ref rectangle.Max, out closestPoint);
            float closestDistanceSquared;
            Vector2.DistanceSquared(ref Center, ref closestPoint, out closestDistanceSquared);
            result = closestDistanceSquared <= (Radius * Radius);
        }

        /// <summary>
        /// 	Checks whether the current BoundingCircle intersects with a specified Halfline.
        /// </summary>
        /// <param name="line"> The Halfline to check for intersection with the current BoundingCircle. </param>
        public float? Intersects(Rayline line)
        {
            return line.Intersects(this);
        }

        /// <summary>
        /// 	Checks whether the current BoundingCircle intersects a Halfline.
        /// </summary>
        /// <param name="line"> The Halfline to check for intersection with. </param>
        /// <param name="result"> [OutAttribute] Distance at which the ray intersects the BoundingCircle or null if there is no intersection. </param>
        public void Intersects(ref Rayline line, out float? result)
        {
            line.Intersects(ref this, out result);
        }

        #endregion

        #region Transform

        /// <summary>
        /// 	Translates and scales the BoundingCircle using a given Matrix.
        /// </summary>
        /// <param name="matrix"> A transformation matrix that might include translation, rotation, or uniform scaling. Note that BoundingCircle.Transform will not return correct results if there are non-uniform scaling, shears, or other unusual transforms in this transformation matrix. This is because there is no way to shear or non-uniformly scale a circle. Such an operation would cause the circle to lose its shape as a circle. </param>
        public BoundingCircle Transform(Matrix matrix)
        {
            var circle = new BoundingCircle
                             {
                                 Center = Vector2.Transform(Center, matrix)
                             };
            var scale1 = (matrix.M11 * matrix.M11) + (matrix.M12 * matrix.M12);
            var scale2 = (matrix.M21 * matrix.M21) + (matrix.M22 * matrix.M22);
            var maxScale = Math.Max(scale1, scale2);
            circle.Radius = Radius * ((float) Math.Sqrt(maxScale));
            return circle;
        }

        /// <summary>
        /// 	Translates and scales the BoundingCircle using a given Matrix.
        /// </summary>
        /// <param name="matrix"> A transformation matrix that might include translation, rotation, or uniform scaling. Note that BoundingCircle.Transform will not return correct results if there are non-uniform scaling, shears, or other unusual transforms in this transformation matrix. This is because there is no way to shear or non-uniformly scale a circle. Such an operation would cause the circle to lose its shape as a circle. </param>
        /// <param name="result"> [OutAttribute] The transformed BoundingCircle. </param>
        public void Transform(ref Matrix matrix, out BoundingCircle result)
        {
            result.Center = Vector2.Transform(Center, matrix);
            var scale1 = (matrix.M11 * matrix.M11) + (matrix.M12 * matrix.M12);
            var scale2 = (matrix.M21 * matrix.M21) + (matrix.M22 * matrix.M22);
            var maxScale = Math.Max(scale1, scale2);
            result.Radius = Radius * ((float) Math.Sqrt(maxScale));
        }

        #endregion
    }
}